/*-------------------------------------------------------------------------
 *
 * common.c
 *	Catalog routines used by pg_dump; long ago these were shared
 *	by another dump tool, but not anymore.
 *
 * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/bin/pg_dump/common.c
 *
 *-------------------------------------------------------------------------
 */
#include "pg_backup_archiver.h"
#include "pg_backup_utils.h"

#include <ctype.h>

#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_extension.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "hashfn.h"

/*
 * Variables for mapping DumpId to DumpableObject
 */
static DumpableObject **dumpIdMap = NULL;
static int	allocedDumpIds = 0;
static DumpId lastDumpId = 0;

/*
 * Infrastructure for mapping CatalogId to DumpableObject
 *
 * We use a hash table generated by simplehash.h.  That infrastructure
 * requires all the hash table entries to be the same size, and it also
 * expects that it can move them around when resizing the table.  So we
 * cannot make the DumpableObjects be elements of the hash table directly;
 * instead, the hash table elements contain pointers to DumpableObjects.
 *
 * It turns out to be convenient to also use this data structure to map
 * CatalogIds to owning extensions, if any.  Since extension membership
 * data is read before creating most DumpableObjects, either one of dobj
 * and ext could be NULL.
 */
typedef struct _catalogIdMapEntry
{
	CatalogId	catId;			/* the indexed CatalogId */
	uint32		status;			/* hash status */
	uint32		hashval;		/* hash code for the CatalogId */
	DumpableObject *dobj;		/* the associated DumpableObject, if any */
	ExtensionInfo *ext;			/* owning extension, if any */
} CatalogIdMapEntry;

#define SH_PREFIX		catalogid
#define SH_ELEMENT_TYPE	CatalogIdMapEntry
#define SH_KEY_TYPE		CatalogId
#define	SH_KEY			catId
#define SH_HASH_KEY(tb, key)	hash_bytes((const unsigned char *) &(key), sizeof(CatalogId))
#define SH_EQUAL(tb, a, b)		((a).oid == (b).oid && (a).tableoid == (b).tableoid)
#define SH_STORE_HASH
#define SH_GET_HASH(tb, a) (a)->hashval
#define	SH_SCOPE		static inline
#define SH_RAW_ALLOCATOR	pg_malloc0
#define SH_DECLARE
#define SH_DEFINE
#include "simplehash.h"

#define CATALOGIDHASH_INITIAL_SIZE	10000

static catalogid_hash *catalogIdHash = NULL;

static void flagInhTables(TableInfo *tbinfo, int numTables,
						  InhInfo *inhinfo, int numInherits);
static void flagInhAttrs(TableInfo *tblinfo, int numTables);
static void findParentsByOid(TableInfo *self,
				 InhInfo *inhinfo, int numInherits);
static int	strInArray(const char *pattern, char **arr, int arr_size);

/*
 * getSchemaData
 *	  Collect information about all potentially dumpable objects
 */
TableInfo *
getSchemaData(Archive *fout, int *numTablesPtr, int binary_upgrade)
{
	TableInfo  *tblinfo;
	ExtensionInfo *extinfo;
	InhInfo    *inhinfo;
	int			numTables;
	int			numTypes;
	int			numFuncs;
	int			numOperators;
	int			numCollations;
	int			numNamespaces;
	int			numExtensions;		
	int			numAggregates;
	int			numInherits;
	int			numRules;
	int			numProcLangs;
	int			numCasts;
	int			numOpclasses;
	int			numOpfamilies;
	int			numConversions;
	int			numTSParsers;
	int			numTSTemplates;
	int			numTSDicts;
	int			numTSConfigs;
	int			numForeignDataWrappers;
	int			numForeignServers;
	int			numDefaultACLs;
	int			numEventTriggers;

	/* GPDB specific variables */

	int			numExtProtocols;

	if (binary_upgrade)
	{
		BinaryUpgradeInfo *binaryUpgradeInfo;
		if (g_verbose)
			write_msg(NULL, "identifying required binary upgrade calls\n");

		binaryUpgradeInfo = newBinaryUpgradeInfo();
	}

	/*
	 * We must read extensions and extension membership info first, because
	 * extension membership needs to be consultable during decisions about
	 * whether other objects are to be dumped.
	 */
	if (g_verbose)
		write_msg(NULL, "reading extensions\n");
	extinfo = getExtensions(fout, &numExtensions);

	if (g_verbose)
		write_msg(NULL, "identifying extension members\n");
	getExtensionMembership(fout, extinfo, numExtensions);

	if (g_verbose)
		write_msg(NULL, "reading schemas\n");
	(void) getNamespaces(fout, &numNamespaces);

	/*
	 * getTables should be done as soon as possible, so as to minimize the
	 * window between starting our transaction and acquiring per-table locks.
	 * However, we have to do getNamespaces first because the tables get
	 * linked to their containing namespaces during getTables.
	 */
	if (g_verbose)
		write_msg(NULL, "reading user-defined tables\n");
	tblinfo = getTables(fout, &numTables);

	getPartitionDefs(fout, tblinfo, numTables);

	getOwnedSeqs(fout, tblinfo, numTables);

	if (g_verbose)
		write_msg(NULL, "reading user-defined functions\n");
	(void) getFuncs(fout, &numFuncs);

	/* this must be after getTables and getFuncs */
	if (g_verbose)
		write_msg(NULL, "reading user-defined types\n");
	(void) getTypes(fout, &numTypes);

	/* this must be after getFuncs, too */
	if (g_verbose)
		write_msg(NULL, "reading procedural languages\n");
	getProcLangs(fout, &numProcLangs);

	if (g_verbose)
		write_msg(NULL, "reading user-defined aggregate functions\n");
	getAggregates(fout, &numAggregates);

	if (g_verbose)
		write_msg(NULL, "reading user-defined operators\n");
	(void) getOperators(fout, &numOperators);

	if (g_verbose)
		write_msg(NULL, "reading user-defined external protocols\n");
	getExtProtocols(fout, &numExtProtocols);

	if (g_verbose)
		write_msg(NULL, "reading user-defined operator classes\n");
	getOpclasses(fout, &numOpclasses);

	if (g_verbose)
		write_msg(NULL, "reading user-defined operator families\n");
	getOpfamilies(fout, &numOpfamilies);

	if (g_verbose)
		write_msg(NULL, "reading user-defined text search parsers\n");
	getTSParsers(fout, &numTSParsers);

	if (g_verbose)
		write_msg(NULL, "reading user-defined text search templates\n");
	getTSTemplates(fout, &numTSTemplates);

	if (g_verbose)
		write_msg(NULL, "reading user-defined text search dictionaries\n");
	getTSDictionaries(fout, &numTSDicts);

	if (g_verbose)
		write_msg(NULL, "reading user-defined text search configurations\n");
	getTSConfigurations(fout, &numTSConfigs);

	if (g_verbose)
		write_msg(NULL, "reading user-defined foreign-data wrappers\n");
	getForeignDataWrappers(fout, &numForeignDataWrappers);

	if (g_verbose)
		write_msg(NULL, "reading user-defined foreign servers\n");
	getForeignServers(fout, &numForeignServers);

	if (g_verbose)
		write_msg(NULL, "reading default privileges\n");
	getDefaultACLs(fout, &numDefaultACLs);

	if (g_verbose)
		write_msg(NULL, "reading user-defined collations\n");
	(void) getCollations(fout, &numCollations);

	if (g_verbose)
		write_msg(NULL, "reading user-defined conversions\n");
	getConversions(fout, &numConversions);

	if (g_verbose)
		write_msg(NULL, "reading type casts\n");
	getCasts(fout, &numCasts);

	if (g_verbose)
		write_msg(NULL, "reading table inheritance information\n");
	inhinfo = getInherits(fout, &numInherits);

	if (g_verbose)
		write_msg(NULL, "reading event triggers\n");
	getEventTriggers(fout, &numEventTriggers);

	/* Identify extension configuration tables that should be dumped */
	if (g_verbose)
		write_msg(NULL, "finding extension tables\n");
	processExtensionTables(fout, extinfo, numExtensions);

	/* Link tables to parents, mark parents of target tables interesting */
	if (g_verbose)
		write_msg(NULL, "finding inheritance relationships\n");
	flagInhTables(tblinfo, numTables, inhinfo, numInherits);

	if (g_verbose)
		write_msg(NULL, "reading column info for interesting tables\n");
	getTableAttrs(fout, tblinfo, numTables);

	if (g_verbose)
		write_msg(NULL, "flagging inherited columns in subtables\n");
	flagInhAttrs(tblinfo, numTables);

	if (g_verbose)
		write_msg(NULL, "reading indexes\n");
	getIndexes(fout, tblinfo, numTables);

	if (binary_upgrade)
	{
		if (g_verbose)
			write_msg(NULL, "reading append-optimized table info\n");
		getAOTableInfo(fout);

		if (g_verbose)
			write_msg(NULL, "reading bitmap index info\n");
		getBMIndxInfo(fout);
	}

	if (g_verbose)
		write_msg(NULL, "reading constraints\n");
	getConstraints(fout, tblinfo, numTables);

	if (g_verbose)
		write_msg(NULL, "reading triggers\n");
	getTriggers(fout, tblinfo, numTables);

	if (g_verbose)
		write_msg(NULL, "reading rewrite rules\n");
	getRules(fout, &numRules);

	free(inhinfo);				/* not needed any longer */

	*numTablesPtr = numTables;
	return tblinfo;
}

/* flagInhTables -
 *	 Fill in parent link fields of every target table, and mark
 *	 parents of target tables as interesting
 *
 * Note that only direct ancestors of targets are marked interesting.
 * This is sufficient; we don't much care whether they inherited their
 * attributes or not.
 *
 * modifies tblinfo
 */
static void
flagInhTables(TableInfo *tblinfo, int numTables,
			  InhInfo *inhinfo, int numInherits)
{
	int			i,
				j;
	int			numParents;
	TableInfo **parents;

	for (i = 0; i < numTables; i++)
	{
		/* Some kinds never have parents */
		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
			tblinfo[i].relkind == RELKIND_VIEW ||
			tblinfo[i].relkind == RELKIND_MATVIEW ||
			tblinfo[i].relstorage == RELSTORAGE_EXTERNAL ||
			tblinfo[i].relstorage == RELSTORAGE_FOREIGN)
			continue;

		/* Don't bother computing anything for non-target tables, either */
		if (!tblinfo[i].dobj.dump)
			continue;

		/* Find all the immediate parent tables */
		findParentsByOid(&tblinfo[i], inhinfo, numInherits);

		/* Mark the parents as interesting for getTableAttrs */
		numParents = tblinfo[i].numParents;
		parents = tblinfo[i].parents;
		for (j = 0; j < numParents; j++)
			parents[j]->interesting = true;
	}
}

/* flagInhAttrs -
 *	 for each dumpable table in tblinfo, flag its inherited attributes
 *
 * What we need to do here is detect child columns that inherit NOT NULL
 * bits from their parents (so that we needn't specify that again for the
 * child) and child columns that have DEFAULT NULL when their parents had
 * some non-null default.  In the latter case, we make up a dummy AttrDefInfo
 * object so that we'll correctly emit the necessary DEFAULT NULL clause;
 * otherwise the backend will apply an inherited default to the column.
 *
 * modifies tblinfo
 */
static void
flagInhAttrs(TableInfo *tblinfo, int numTables)
{
	int			i,
				j,
				k;

	for (i = 0; i < numTables; i++)
	{
		TableInfo  *tbinfo = &(tblinfo[i]);
		int			numParents;
		TableInfo **parents;

		/* Some kinds never have parents */
		if (tbinfo->relkind == RELKIND_SEQUENCE ||
			tbinfo->relkind == RELKIND_VIEW ||
			tbinfo->relkind == RELKIND_MATVIEW ||
			tbinfo->relstorage == RELSTORAGE_EXTERNAL ||
			tbinfo->relstorage == RELSTORAGE_FOREIGN)
			continue;

		/* Don't bother computing anything for non-target tables, either */
		if (!tbinfo->dobj.dump)
			continue;

		numParents = tbinfo->numParents;
		parents = tbinfo->parents;

		if (numParents == 0)
			continue;			/* nothing to see here, move along */

		/* For each column, search for matching column names in parent(s) */
		for (j = 0; j < tbinfo->numatts; j++)
		{
			bool		foundNotNull;	/* Attr was NOT NULL in a parent */
			bool		foundDefault;	/* Found a default in a parent */

			/* no point in examining dropped columns */
			if (tbinfo->attisdropped[j])
				continue;

			foundNotNull = false;
			foundDefault = false;
			for (k = 0; k < numParents; k++)
			{
				TableInfo  *parent = parents[k];
				int			inhAttrInd;

				inhAttrInd = strInArray(tbinfo->attnames[j],
										parent->attnames,
										parent->numatts);
				if (inhAttrInd >= 0)
				{
					foundNotNull |= parent->notnull[inhAttrInd];
					foundDefault |= (parent->attrdefs[inhAttrInd] != NULL);
				}
			}

			/* Remember if we found inherited NOT NULL */
			tbinfo->inhNotNull[j] = foundNotNull;

			/* Manufacture a DEFAULT NULL clause if necessary */
			if (foundDefault && tbinfo->attrdefs[j] == NULL)
			{
				AttrDefInfo *attrDef;

				attrDef = (AttrDefInfo *) malloc(sizeof(AttrDefInfo));
				attrDef->dobj.objType = DO_ATTRDEF;
				attrDef->dobj.catId.tableoid = 0;
				attrDef->dobj.catId.oid = 0;
				AssignDumpId(&attrDef->dobj);
				attrDef->dobj.name = strdup(tbinfo->dobj.name);
				attrDef->dobj.namespace = tbinfo->dobj.namespace;
				attrDef->dobj.dump = tbinfo->dobj.dump;

				attrDef->adtable = tbinfo;
				attrDef->adnum = j + 1;
				attrDef->adef_expr = strdup("NULL");

				/* Will column be dumped explicitly? */
				if (shouldPrintColumn(tbinfo, j))
				{
					attrDef->separate = false;
					/* No dependency needed: NULL cannot have dependencies */
				}
				else
				{
					/* column will be suppressed, print default separately */
					attrDef->separate = true;
					/* ensure it comes out after the table */
					addObjectDependency(&attrDef->dobj,
										tbinfo->dobj.dumpId);
				}

				tbinfo->attrdefs[j] = attrDef;
			}
		}
	}
}

/*
 * AssignDumpId
 *		Given a newly-created dumpable object, assign a dump ID,
 *		and enter the object into the lookup tables.
 *
 * The caller is expected to have filled in objType and catId,
 * but not any of the other standard fields of a DumpableObject.
 */
void
AssignDumpId(DumpableObject *dobj)
{
	dobj->dumpId = ++lastDumpId;
	dobj->name = NULL;			/* must be set later */
	dobj->namespace = NULL;		/* may be set later */
	dobj->dump = true;			/* default assumption */
	dobj->ext_member = false;	/* default assumption */
	dobj->dependencies = NULL;
	dobj->nDeps = 0;
	dobj->allocDeps = 0;

	/* Add object to dumpIdMap[], enlarging that array if need be */
	while (dobj->dumpId >= allocedDumpIds)
	{
		int			newAlloc;

		if (allocedDumpIds <= 0)
		{
			newAlloc = 256;
			dumpIdMap = (DumpableObject **)
				pg_malloc(newAlloc * sizeof(DumpableObject *));
		}
		else
		{
			newAlloc = allocedDumpIds * 2;
			dumpIdMap = (DumpableObject **)
				pg_realloc(dumpIdMap, newAlloc * sizeof(DumpableObject *));
		}
		memset(dumpIdMap + allocedDumpIds, 0,
			   (newAlloc - allocedDumpIds) * sizeof(DumpableObject *));
		allocedDumpIds = newAlloc;
	}
	dumpIdMap[dobj->dumpId] = dobj;

	/* If it has a valid CatalogId, enter it into the hash table */
	if (OidIsValid(dobj->catId.tableoid))
	{
		CatalogIdMapEntry *entry;
		bool		found;

		/* Initialize CatalogId hash table if not done yet */
		if (catalogIdHash == NULL)
			catalogIdHash = catalogid_create(CATALOGIDHASH_INITIAL_SIZE, NULL);

		entry = catalogid_insert(catalogIdHash, dobj->catId, &found);
		if (!found)
		{
			entry->dobj = NULL;
			entry->ext = NULL;
		}
		Assert(entry->dobj == NULL);
		entry->dobj = dobj;
	}
}

/*
 * Assign a DumpId that's not tied to a DumpableObject.
 *
 * This is used when creating a "fixed" ArchiveEntry that doesn't need to
 * participate in the sorting logic.
 */
DumpId
createDumpId(void)
{
	return ++lastDumpId;
}

/*
 * Return the largest DumpId so far assigned
 */
DumpId
getMaxDumpId(void)
{
	return lastDumpId;
}

/*
 * Find a DumpableObject by dump ID
 *
 * Returns NULL for invalid ID
 */
DumpableObject *
findObjectByDumpId(DumpId dumpId)
{
	if (dumpId <= 0 || dumpId >= allocedDumpIds)
		return NULL;			/* out of range? */
	return dumpIdMap[dumpId];
}

/*
 * Find a DumpableObject by catalog ID
 *
 * Returns NULL for unknown ID
 */
DumpableObject *
findObjectByCatalogId(CatalogId catalogId)
{
	CatalogIdMapEntry *entry;

	if (catalogIdHash == NULL)
		return NULL;			/* no objects exist yet */

	entry = catalogid_lookup(catalogIdHash, catalogId);
	if (entry == NULL)
		return NULL;
	return entry->dobj;
}

/*
 * Build an array of pointers to all known dumpable objects
 *
 * This simply creates a modifiable copy of the internal map.
 */
void
getDumpableObjects(DumpableObject ***objs, int *numObjs)
{
	int			i,
				j;

	*objs = (DumpableObject **)
		pg_malloc(allocedDumpIds * sizeof(DumpableObject *));
	j = 0;
	for (i = 1; i < allocedDumpIds; i++)
	{
		if (dumpIdMap[i])
			(*objs)[j++] = dumpIdMap[i];
	}
	*numObjs = j;
}

/*
 * Add a dependency link to a DumpableObject
 *
 * Note: duplicate dependencies are currently not eliminated
 */
void
addObjectDependency(DumpableObject *dobj, DumpId refId)
{
	if (dobj->nDeps >= dobj->allocDeps)
	{
		if (dobj->allocDeps <= 0)
		{
			dobj->allocDeps = 16;
			dobj->dependencies = (DumpId *)
				pg_malloc(dobj->allocDeps * sizeof(DumpId));
		}
		else
		{
			dobj->allocDeps *= 2;
			dobj->dependencies = (DumpId *)
				pg_realloc(dobj->dependencies,
						   dobj->allocDeps * sizeof(DumpId));
		}
	}
	dobj->dependencies[dobj->nDeps++] = refId;
}

/*
 * Remove a dependency link from a DumpableObject
 *
 * If there are multiple links, all are removed
 */
void
removeObjectDependency(DumpableObject *dobj, DumpId refId)
{
	int			i;
	int			j = 0;

	for (i = 0; i < dobj->nDeps; i++)
	{
		if (dobj->dependencies[i] != refId)
			dobj->dependencies[j++] = dobj->dependencies[i];
	}
	dobj->nDeps = j;
}

/*
 * findTableByOid
 *	  finds the DumpableObject for the table with the given oid
 *	  returns NULL if not found
 */
TableInfo *
findTableByOid(Oid oid)
{
	CatalogId	catId;
	DumpableObject *dobj;

	catId.tableoid = RelationRelationId;
	catId.oid = oid;
	dobj = findObjectByCatalogId(catId);
	Assert(dobj == NULL || dobj->objType == DO_TABLE);
	return (TableInfo *) dobj;
}

/*
 * findIndexByOid
 *	  finds the DumpableObject for the index with the given oid
 *	  returns NULL if not found
 */
IndxInfo *
findIndexByOid(Oid oid)
{
	CatalogId	catId;
	DumpableObject *dobj;

	catId.tableoid = RelationRelationId;
	catId.oid = oid;
	dobj = findObjectByCatalogId(catId);
	Assert(dobj == NULL || dobj->objType == DO_INDEX);
	return (IndxInfo *) dobj;
}

/*
 * findTypeByOid
 *	  finds the DumpableObject for the type with the given oid
 *	  returns NULL if not found
 */
TypeInfo *
findTypeByOid(Oid oid)
{
	CatalogId	catId;
	DumpableObject *dobj;

	catId.tableoid = TypeRelationId;
	catId.oid = oid;
	dobj = findObjectByCatalogId(catId);
	Assert(dobj == NULL ||
		   dobj->objType == DO_TYPE || dobj->objType == DO_DUMMY_TYPE);
	return (TypeInfo *) dobj;
}

/*
 * findFuncByOid
 *	  finds the DumpableObject for the function with the given oid
 *	  returns NULL if not found
 */
FuncInfo *
findFuncByOid(Oid oid)
{
	CatalogId	catId;
	DumpableObject *dobj;

	catId.tableoid = ProcedureRelationId;
	catId.oid = oid;
	dobj = findObjectByCatalogId(catId);
	Assert(dobj == NULL || dobj->objType == DO_FUNC);
	return (FuncInfo *) dobj;
}

/*
 * findOprByOid
 *	  finds the DumpableObject for the operator with the given oid
 *	  returns NULL if not found
 */
OprInfo *
findOprByOid(Oid oid)
{
	CatalogId	catId;
	DumpableObject *dobj;

	catId.tableoid = OperatorRelationId;
	catId.oid = oid;
	dobj = findObjectByCatalogId(catId);
	Assert(dobj == NULL || dobj->objType == DO_OPERATOR);
	return (OprInfo *) dobj;
}

/*
 * findCollationByOid
 *	  finds the DumpableObject for the collation with the given oid
 *	  returns NULL if not found
 */
CollInfo *
findCollationByOid(Oid oid)
{
	CatalogId	catId;
	DumpableObject *dobj;

	catId.tableoid = CollationRelationId;
	catId.oid = oid;
	dobj = findObjectByCatalogId(catId);
	Assert(dobj == NULL || dobj->objType == DO_COLLATION);
	return (CollInfo *) dobj;
}

/*
 * findNamespaceByOid
 *	  finds the DumpableObject for the namespace with the given oid
 *	  returns NULL if not found
 */
NamespaceInfo *
findNamespaceByOid(Oid oid)
{
	CatalogId	catId;
	DumpableObject *dobj;

	catId.tableoid = NamespaceRelationId;
	catId.oid = oid;
	dobj = findObjectByCatalogId(catId);
	Assert(dobj == NULL || dobj->objType == DO_NAMESPACE);
	return (NamespaceInfo *) dobj;
}

/*
 * findExtensionByOid
 *	  finds the DumpableObject for the extension with the given oid
 *	  returns NULL if not found
 */
ExtensionInfo *
findExtensionByOid(Oid oid)
{
	CatalogId	catId;
	DumpableObject *dobj;

	catId.tableoid = ExtensionRelationId;
	catId.oid = oid;
	dobj = findObjectByCatalogId(catId);
	Assert(dobj == NULL || dobj->objType == DO_EXTENSION);
	return (ExtensionInfo *) dobj;
}


/*
 * recordExtensionMembership
 *	  Record that the object identified by the given catalog ID
 *	  belongs to the given extension
 */
void
recordExtensionMembership(CatalogId catId, ExtensionInfo *ext)
{
	CatalogIdMapEntry *entry;
	bool		found;

	/* CatalogId hash table must exist, if we have an ExtensionInfo */
	Assert(catalogIdHash != NULL);

	/* Add reference to CatalogId hash */
	entry = catalogid_insert(catalogIdHash, catId, &found);
	if (!found)
	{
		entry->dobj = NULL;
		entry->ext = NULL;
	}
	Assert(entry->ext == NULL);
	entry->ext = ext;
}

/*
 * findOwningExtension
 *	  return owning extension for specified catalog ID, or NULL if none
 */
ExtensionInfo *
findOwningExtension(CatalogId catalogId)
{
	CatalogIdMapEntry *entry;

	if (catalogIdHash == NULL)
		return NULL;			/* no objects exist yet */

	entry = catalogid_lookup(catalogIdHash, catalogId);
	if (entry == NULL)
		return NULL;
	return entry->ext;
}


/*
 * findParentsByOid
 *	  find a table's parents in tblinfo[]
 */
static void
findParentsByOid(TableInfo *self,
				 InhInfo *inhinfo, int numInherits)
{
	Oid			oid = self->dobj.catId.oid;
	int			i,
				j;
	int			numParents;

	numParents = 0;
	for (i = 0; i < numInherits; i++)
	{
		if (inhinfo[i].inhrelid == oid)
			numParents++;
	}

	self->numParents = numParents;

	if (numParents > 0)
	{
		self->parents = (TableInfo **)
			pg_malloc(sizeof(TableInfo *) * numParents);
		j = 0;
		for (i = 0; i < numInherits; i++)
		{
			if (inhinfo[i].inhrelid == oid)
			{
				TableInfo  *parent;

				parent = findTableByOid(inhinfo[i].inhparent);
				if (parent == NULL)
				{
					write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
							  inhinfo[i].inhparent,
							  self->dobj.name,
							  oid);
					exit_nicely(1);
				}
				self->parents[j++] = parent;
			}
		}
	}
	else
		self->parents = NULL;
}

/*
 * parseOidArray
 *	  parse a string of numbers delimited by spaces into a character array
 *
 * Note: actually this is used for both Oids and potentially-signed
 * attribute numbers.  This should cause no trouble, but we could split
 * the function into two functions with different argument types if it does.
 */

void
parseOidArray(const char *str, Oid *array, int arraysize)
{
	int			j,
				argNum;
	char		temp[100];
	char		s;

	argNum = 0;
	j = 0;
	for (;;)
	{
		s = *str++;
		if (s == ' ' || s == '\0')
		{
			if (j > 0)
			{
				if (argNum >= arraysize)
				{
					write_msg(NULL, "could not parse numeric array \"%s\": too many numbers\n", str);
					exit_nicely(1);
				}
				temp[j] = '\0';
				array[argNum++] = atooid(temp);
				j = 0;
			}
			if (s == '\0')
				break;
		}
		else
		{
			if (!(isdigit((unsigned char) s) || s == '-') ||
				j >= sizeof(temp) - 1)
			{
				write_msg(NULL, "could not parse numeric array \"%s\": invalid character in number\n", str);
				exit_nicely(1);
			}
			temp[j++] = s;
		}
	}

	while (argNum < arraysize)
		array[argNum++] = InvalidOid;
}


/*
 * strInArray:
 *	  takes in a string and a string array and the number of elements in the
 * string array.
 *	  returns the index if the string is somewhere in the array, -1 otherwise
 */

static int
strInArray(const char *pattern, char **arr, int arr_size)
{
	int			i;

	for (i = 0; i < arr_size; i++)
	{
		if (strcmp(pattern, arr[i]) == 0)
			return i;
	}
	return -1;
}


/*
 * Support for simple list operations
 */

void
simple_oid_list_append(SimpleOidList *list, Oid val)
{
	SimpleOidListCell *cell;

	cell = (SimpleOidListCell *) pg_malloc(sizeof(SimpleOidListCell));
	cell->next = NULL;
	cell->val = val;

	if (list->tail)
		list->tail->next = cell;
	else
		list->head = cell;
	list->tail = cell;
}

bool
simple_oid_list_member(SimpleOidList *list, Oid val)
{
	SimpleOidListCell *cell;

	for (cell = list->head; cell; cell = cell->next)
	{
		if (cell->val == val)
			return true;
	}
	return false;
}

/*
 * MPP-1890
 *
 * If the user explicitly DROP'ed a CHECK constraint on a child but it
 * still exists on the parent when they dump and restore that constraint
 * will exist on the child since it will again inherit it from the
 * parent. Therefore we look here for constraints that exist on the
 * parent but not on the child and mark them to be dropped from the
 * child after the child table is defined.
 *
 * Loop through each parent and for each parent constraint see if it
 * exists on the child as well. If it doesn't it means that the child
 * dropped it. Mark it.
 */
void
DetectChildConstraintDropped(TableInfo *tbinfo, PQExpBuffer q)
{
	TableInfo  *parent;
	TableInfo **parents = tbinfo->parents;
	int			j,
				k,
				l;
	int			numParents = tbinfo->numParents;

	for (k = 0; k < numParents; k++)
	{
		parent = parents[k];

		/* for each CHECK constraint of this parent */
		for (l = 0; l < parent->ncheck; l++)
		{
			ConstraintInfo *pconstr = &(parent->checkexprs[l]);
			ConstraintInfo *cconstr;
			bool		constr_on_child = false;

			/* for each child CHECK constraint */
			for (j = 0; j < tbinfo->ncheck; j++)
			{
				cconstr = &(tbinfo->checkexprs[j]);

				if (strcmp(pconstr->dobj.name, cconstr->dobj.name) == 0)
				{
					/* parent constr exists on child. hence wasn't dropped */
					constr_on_child = true;
					break;
				}

			}

			/* this parent constr is not on child, issue a DROP for it */
			if (!constr_on_child)
			{
				appendPQExpBuffer(q, "ALTER TABLE %s.",
								  fmtId(tbinfo->dobj.namespace->dobj.name));
				appendPQExpBuffer(q, "%s ",
								  fmtId(tbinfo->dobj.name));
				appendPQExpBuffer(q, "DROP CONSTRAINT %s;\n",
								  fmtId(pconstr->dobj.name));

				constr_on_child = false;
			}
		}
	}

}
